package model;

import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;

/**
 * Class represents a Datastructure to store and manage pricesteps created by a
 * management client for the billing server. It has an inner class pricestep
 * that represents a single pricestep. Pricesteps class holds a list of
 * pricesteps
 */
public class PriceSteps implements Serializable {

	private static final long serialVersionUID = 6523545953899526167L;
	private List<PriceStep> steps;

	public PriceSteps() {
		this.steps = Collections.synchronizedList(new ArrayList<PriceStep>());
	}

	/**
	 * Adds a new PriceStep to the PriceSteps
	 * 
	 * @param startPrice
	 *            The startprice where the pricestep starts.
	 * @param endPrice
	 *            The price where the steps ends.
	 * @param fixedPrice
	 *            The fixed auction fee to pay for this step.
	 * @param variablePricePercent
	 *            The variable auction fee to pay for this step.
	 * @throws IllegalArgumentException
	 *             Thrown if any of the arguments is negativ or if the new step
	 *             overlaps with an existing one.
	 */
	public synchronized void addPriceStep(double startPrice, double endPrice,
			double fixedPrice, double variablePricePercent)
			throws IllegalArgumentException {
		if (startPrice < 0 || endPrice < 0 || fixedPrice < 0
				|| variablePricePercent < 0) {
			throw new IllegalArgumentException(
					"All arguments for a new Pricestep must not be negative.");
		}
		if (startPrice >= endPrice && endPrice != 0) {
			throw new IllegalArgumentException(
					"Input for endprice must be bigger than startprice.");
		}
		PriceStep newStep = new PriceStep(startPrice, endPrice, fixedPrice,
				variablePricePercent);
		for (PriceStep p : this.steps) {
			if (newStep.overlaps(p)) {
				String endPriceString = "" + p.getEndPrice();
				if (p.getEndPrice() == 0) {
					endPriceString = "INFINITY";
				}
				throw new IllegalArgumentException(
						"New Pricestep overlaps with existing one: "
								+ p.getStartPrice() + " - " + endPriceString);
			}
		}
		this.steps.add(newStep);
	} // end of addPriceStep

	/**
	 * Removes a pricestep from the existing pricesteps.
	 * 
	 * @param startPrice
	 *            The startprice of the step that should be removed.
	 * @param endPrice
	 *            The endprice of the step that should be removed.
	 * @throws IllegalArgumentException
	 *             Thrown if the step that shall be removed does not exist.
	 */
	public synchronized void removePriceStep(double startPrice, double endPrice)
			throws IllegalArgumentException {
		PriceStep toRemove = null;
		for (PriceStep p : this.steps) {
			if (p.matches(startPrice, endPrice)) {
				toRemove = p;
			}
		}
		if (toRemove == null) {
			throw new IllegalArgumentException("A pricestep with interval "
					+ startPrice + " to " + endPrice + " does not exist.");
		}
		this.steps.remove(toRemove);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		if (this.steps.size() == 0) {
			return "No pricesteps where specified yet.";
		}
		StringBuilder builder = new StringBuilder();
		builder.append("Min_Price Max_Price Fee_Fixed Fee_Variable\n");
		Collections.sort(this.steps);
		for (PriceStep p : this.steps) {
			builder.append(p.toString());
		}
		String result = builder.toString();
		return result.substring(0, result.length() - 1);
	}

	public double getFixedFee(double price) {
		for (PriceStep step : this.steps) {
			if (step.getStartPrice() <= price && (step.getEndPrice() >= price || step.getEndPrice() == 0)) {
				return step.getFixedPrice();
			}
		}
		return 0;
	}

	public double getVariableFee(double price) {
		for (PriceStep step : this.steps) {
			if (step.getStartPrice() <= price && (step.getEndPrice() >= price || step.getEndPrice() == 0)) {
				return (price / 100) * step.getVariablePricePercent();
			}
		}
		return 0;
	}

	/**
	 * Inner Class represents a single Pricestep, it is an interval where to pay
	 * a specific fixed or variable fee for the successful auction.
	 */
	class PriceStep implements Comparable<PriceStep>, Serializable {

		private static final long serialVersionUID = 3467208318085354049L;
		double startPrice, endPrice, fixedPrice, variablePricePercent;

		PriceStep(double startPrice, double endPrice, double fixedPrice,
				double variablePricePercent) {
			this.startPrice = startPrice;
			this.endPrice = endPrice;
			this.fixedPrice = fixedPrice;
			this.variablePricePercent = variablePricePercent;
		}

		public double getStartPrice() {
			return this.startPrice;
		}

		public double getEndPrice() {
			return this.endPrice;
		}

		public double getFixedPrice() {
			return this.fixedPrice;
		}

		public double getVariablePricePercent() {
			return this.variablePricePercent;
		}

		/**
		 * Tests if an pricestep interval overlaps with another pricestep.
		 * 
		 * @param other
		 *            The other pricestep.
		 * @return True if overlaps, false else.
		 */
		public boolean overlaps(PriceStep other) {
			if (this.endPrice == 0 && other.endPrice == 0) {
				return true;
			}
			if (this.endPrice == 0) {
				return !(other.endPrice <= this.startPrice);
			}
			if (other.endPrice == 0) {
				return !(this.endPrice <= other.startPrice);
			}
			return !(this.endPrice <= other.startPrice || this.startPrice >= other.endPrice);
		}

		/**
		 * Tests if a pricestep has a specific interval.
		 * 
		 * @param startPrice
		 *            The startprice the pricestep shall have.
		 * @param endPrice
		 *            The endprice the pricestep shall have.
		 * @return True if pricestep has that interval, false else.
		 */
		public boolean matches(double startPrice, double endPrice) {
			return this.startPrice == startPrice && this.endPrice == endPrice;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#toString()
		 */
		@Override
		public String toString() {
			Double start = new Double(this.startPrice);
			Double end = new Double(this.endPrice);
			Double fixed = new Double(this.fixedPrice);
			Double variable = new Double(this.variablePricePercent);
			if (this.endPrice > 0) {
				return "" + start + getSpaces(10 - start.toString().length())
						+ end + getSpaces(10 - end.toString().length()) + fixed
						+ getSpaces(10 - fixed.toString().length()) + variable
						+ "%" + getSpaces(9 - variable.toString().length())
						+ "\n";
			}
			return "" + start + getSpaces(10 - start.toString().length())
					+ "INFINITY" + getSpaces(2) + fixed
					+ getSpaces(10 - fixed.toString().length()) + variable
					+ "%" + getSpaces(9 - variable.toString().length()) + "\n";

		}

		/**
		 * Creates a string that consists of a specific number of whitespaces.
		 * 
		 * @param num
		 *            The number of whitespaces the string shall have.
		 * @return The resulting String.
		 */
		private String getSpaces(int num) {
			String result = "";
			if (num > 1) {
				for (int i = 0; i < num; i++) {
					result += " ";
				}
				return result;
			}
			return " ";

		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Comparable#compareTo(java.lang.Object)
		 */
		@Override
		public int compareTo(PriceStep other) {
			if (this.startPrice < other.startPrice) {
				return -1;
			}
			if (this.startPrice > other.startPrice) {
				return 1;
			}
			return 0;
		}
	}

}
